#include "BigQ.h"
#include "TwoWayList.h"

#define THREAD_RUNNING 1
#define THREAD_EXITING 0

#define THREAD_ERROR 0
#define INPUT_ERROR 1


static void *runFunction(void * arg){
     BigQ *bq = (BigQ *)arg;
     bq->run();
     cout << "OOH!! HERE" << endl;
     bq->ShutdownPipe();
     pthread_exit(NULL);
}

//Compare related definitions

BigQ::Compare::Compare (){ /* LOLZ I CREATE NOTHING  */ }

BigQ::Compare::Compare(OrderMaker &sort){
	orderBy = sort;
}
BigQ::Compare::~Compare (){ /* LOLZ I DESTROY NOTHING*/ }

bool BigQ::Compare::operator ()(Record *compleft, Record *compright){
	 ComparisonEngine engine;
	 return (engine.Compare(compleft, compright, &orderBy) >= 0);
}


// BigQ specific definitions

BigQ::BigQ(){ /* Adding this is pointless... but G++ complains... */ }

BigQ::BigQ(OrderMaker &sortOrder, int runlen){
	in = new Pipe(100);
	out = new Pipe(100);
	InitArgs(sortOrder, runlen);
	if(pthread_create(&workerThread, NULL, runFunction, (void *)this) != 0) HandleError(THREAD_ERROR);
}

BigQ::BigQ (Pipe &inPipe, Pipe &outPipe, OrderMaker &sortorder, int runlen){
     InitArgs(inPipe, outPipe, sortorder, runlen);
     if(pthread_create(&workerThread, NULL, runFunction, (void *)this) != 0) HandleError(THREAD_ERROR);
     //out->ShutDown();
}

BigQ::~BigQ(){ /* This does nothing but make G++ happy */ }
void BigQ:: ShutdownPipe(){
	cout << "Out Pipe: ";
	out->ShutDown();
}
void BigQ::CloseInPipe(){
	cout << "In Pipe: ";
	in->ShutDown();
}

int BigQ:: GetNext(Record *record){
	return out->Remove(record);
}

void BigQ::InitArgs(Pipe &inPipe, Pipe &outPipe, OrderMaker &sort, int runlen){
     in = &inPipe;
     out = &outPipe;
     sortOrder = sort;
     runLength = runlen;

     pageNum = 0;
     currentPageSize = 0;
     threadStatus = THREAD_RUNNING;
     tempPage = new Page();
     location = "temp.dat";
     file.Open(0, location);
}

void BigQ::InitArgs(OrderMaker &sort, int runlen){
	 sortOrder = sort;
	 runLength = runlen;

	 pageNum = 0;
	 currentPageSize = 0;
	 threadStatus = THREAD_RUNNING;
	 tempPage = new Page();
	 location = "temp.dat";
	 file.Open(0, location);
}

//This is the main thread routine, calls InputRoutine and OutputRoutine
void * BigQ::run(){
	 InputRoutine();
	 SortRoutine();
	 OutputRoutine();
}

// Input routine for worker thread: Grabs records from input pipe and blocks if unavailable.
// Stays in this routine until the pipe is shutdown
void * BigQ::InputRoutine(){
	 cout << "InputRoutine" << endl;
	 int counter = 0;
	 Record current;
	 tempList = new TwoWayList<Record>();
	 while(in->Remove(&current)){
          if(BitsWillFitPage(current.GetBits())){
        	  ++counter;
        	  cout << "Sorted Page Insert" << endl;
        	  SortedPageInsert(current);
          }
          else{
              if(pageNum + 1 < runLength){
            	  cout << "Here is an issue" << endl;
            	  AddFromListToPage();
            	  AddPageAndSetup();
                  ++counter;
            	  SortedPageInsert(current);
              }
          }
	 }

	 cout << "Number of records " << counter << endl;
	 AddFromListToPage();
	 AddPageAndSetup();
	 cout << "Number of Pages: " << pageNum << endl;
	 file.Close();
}

// This is the sorting routine for the worker thread
void * BigQ::SortRoutine(){
	cout << "Sort Routine" << endl;
     // This is where we need to create the priority queue
	 //      To do this we need to have our comparison setup... (using comparisonEngine perhaps...)
	 //      We also need to funnel records from the page to this queue
	 //      This shouldn't be too bad considering this will be an internal process
     file.Open(1, location);
     sorter = new priority_queue<Record*, deque<Record*>, Compare >(sortOrder);
	 SortingSetup();
	 Record temp;
	 pageNum = 1;
	 numPages = file.GetLength() - 1;
	 file.GetPage(tempPage, pageNum);
	 while(CanGrabRecordFromPage(&temp)){
		 Record *store = new Record();
		 store->Copy(&temp);
		 sorter->push(store);
	 }
	 // Should run quicksort here...
	 cout << "Sorter Size: "<<  sorter->size() << endl;
}

// Output routine for worker thread: shoves records to output pipe
void * BigQ::OutputRoutine(){
	Record *temp;
	cout << "In output" << endl;
	while(!sorter->empty()){
		temp = ConstToNonConst(sorter->top());
		out->Insert(temp);
		sorter->pop();
	}
	threadStatus = THREAD_EXITING;
}


Record *BigQ::ConstToNonConst(const Record *record){
	return const_cast<Record *>(record);
}

// Returns a 1 if the bits will fit within the current page, and 0 if they wont
int BigQ::BitsWillFitPage(char * bits){
     return (PAGE_SIZE - currentPageSize >= ((int *) bits)[0]);
}
// Insert into a PageList in a sorted fashion
void BigQ::SortedPageInsert(Record &record){
     cout << "BEGIN BIGQ.SORTEDPAGEINSERT" << endl;
     cout << "RECORD's BITSIZE: " << ((int *)(record.GetBits()))[0] << endl;
     Record temp;
     temp.Consume(&record);
     cout << "TEMP's BITSIZE: " << ((int *)(temp.GetBits()))[0] << endl;
     while(CantInsertHere(temp)){
          cout << tempList->LeftLength() << "<--left | right -->" << tempList->RightLength() << endl;
    	  tempList->Advance();
	  cout << "Whats going on" << endl;
          cout << tempList->RightLength()<< endl;
     }
     currentPageSize += ((int *) (temp.GetBits()))[0];
     cout << "CAN GET TEMP'S BITS" << endl;
     tempList->Insert(&temp);
     cout << "CAN INSERT TEMP" << endl;
     tempList->MoveToStart();
     cout << "CAN MOVE TO START" << endl;
     cout << "END OF BIGQ.SORTEDPAGEINSERT " << endl;

}

void BigQ::AddToPipe(Record *record){
        cout << "BEGIN BIGQ.ADDTOPIPE" << endl;
	Record temp;
	temp.Consume(record);
	in->Insert(&temp);
	cout << "END BIGQ.ADDTOPIPE" << endl;
}

void BigQ::AddFromListToPage(){
        cout << "BEGIN BIGQ.ADDFROMLISTTOPAGE" << endl;
	Record temp;
	tempList->MoveToStart();
	while(TempListIsntEmpty()){
		tempList->Remove(&temp);
		tempPage->Append(&temp);
	}
        cout << "END BIGQ.ADDFROMLISTTOPAGE" << endl;
}

int BigQ::CanGrabRecordFromPage(Record *record){
	if(tempPage->GetFirst(record)) return 1;
	else if(pageNum + 2 >= numPages)  return 0;
	else{
		file.GetPage(tempPage, ++pageNum);
		return CanGrabRecordFromPage(record);
	}
}


int BigQ::TempListIsntEmpty(){
	return !(tempList->LeftLength() == 0 && tempList->RightLength() == 0);
}

void BigQ::AddPageAndSetup(){

     file.AddPage(tempPage, ++pageNum);
     tempPage = new Page();
     currentPageSize = 0;
}

void BigQ::SortingSetup(){
     file.GetPage(tempPage, 0);
     pageNum = 0;
}

// Check to see if we are unable to insert the record in the current position of the list
int BigQ::CantInsertHere(Record &current){
     //First check to see if the list is empty
	 if (!TempListIsntEmpty()) return 0;
	 else{
		 // Check to see if we are at the end
		 if(tempList->RightLength() == 0) return 0;        
		 // GENERAL CASE
		 else return (CompareCurrent(current));
	 }
}
// Compare the current pageList Record with our record for insertion.
// Returns 0 if good and 1 if bad
int BigQ::CompareCurrent(Record &record){
     return (engine.Compare(&record,tempList->Current(0), &sortOrder)) >= 0;
}

// Overall Error Handling function for BigQ.
void BigQ::HandleError(int errorType){
     cout << "ERROR ENCOUNTERED: " << endl
          << ((errorType == THREAD_ERROR)? "There seems to have been a problem initializing the thread.":
        	  (errorType == INPUT_ERROR)? "There was an error placing this record into the list/page.":
        	  "I don't know how the hell we got here... we need to figure out what's wrong.") << endl;
     exit(1);
}
